#include "minidb.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <direct.h>

#pragma warning(disable:4996)

#define MINIDB_DEBUG(fmt, ...) printf("%s:%d fn:%s " fmt "\r\n", strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__);

typedef struct _mini_item_info {
	int rid;
	int index;
	int offset;
	int len;
} MINI_ITEM_INFO;

typedef struct _mini_hdr {
	int used_item_count;
	int all_item_count;
	int db_version;
	int check_sum;
} MINI_HDR;

static MINI_HDR* g_hdr_img = 0;
static const char* g_db_dir = 0;

static int mini_db_already_exist(int rid, int idx, MINI_ITEM_INFO** it) {
	int i = 0;
	MINI_ITEM_INFO* p = (MINI_ITEM_INFO*)((char*)g_hdr_img + sizeof(MINI_HDR));
	for (i = 0; i < g_hdr_img->used_item_count; ++i) {
		if (rid == p[i].rid && idx == p[i].index) {
			*it = &p[i];
			return 1;
		}
	}
	return 0;
}

static int mini_db_update_data(MINI_ITEM_INFO* it, char* buf, int size) {
	FILE* f = 0;
	if (it->len != size) {
		MINIDB_DEBUG("[ERR] size is no eq size in db. abort.");
		return -1;
	}

	f = fopen(g_db_dir, "rb+");
	if (!f) {
		MINIDB_DEBUG("[ERR] open db failed.");
		return -1;
	}
	fseek(f, it->offset, SEEK_SET);
	if (1 != fwrite(buf, size, 1, f)) {
		MINIDB_DEBUG("[ERR] write db failed.");
		fclose(f);
		return -1;
	}
	fclose(f);
	return 0;
}

static int minidb_count_reach_max() {
	return g_hdr_img->used_item_count == g_hdr_img->all_item_count;
}

static int minidb_realoc() {
	MINI_ITEM_INFO* p = NULL;
	int i = 0;
	FILE* f = NULL;
	int data_len = 0;
	char* data_buf = NULL;

	int old_cnt = g_hdr_img->all_item_count;
	int new_cnt = g_hdr_img->all_item_count * 2;
	g_hdr_img->all_item_count = new_cnt;
	g_hdr_img = (MINI_HDR*)realloc(g_hdr_img, sizeof(MINI_HDR) + new_cnt * sizeof(MINI_ITEM_INFO));
	p = (MINI_ITEM_INFO*)((char*)g_hdr_img + sizeof(MINI_HDR));
	memset(&p[old_cnt], 0, sizeof(MINI_ITEM_INFO) * old_cnt);

	//update existed item's offset
	for (i = 0; i < old_cnt; i++) {
		p[i].offset += old_cnt * sizeof(MINI_ITEM_INFO);
	}

	f = fopen(g_db_dir, "rb");
	fseek(f, 0, SEEK_END);
	data_len = ftell(f) - sizeof(MINI_HDR) - sizeof(MINI_ITEM_INFO) * old_cnt;
	data_buf = (char*)malloc(data_len);
	fseek(f, sizeof(MINI_HDR) + sizeof(MINI_ITEM_INFO) * old_cnt, SEEK_SET);
	if (1 != fread(data_buf, data_len, 1, f)) {
		MINIDB_DEBUG("[ERR] read db failed.");
		fclose(f);
		return -1;
	}
	fclose(f);

	//empty file. and rewrite
	f = fopen(g_db_dir, "wb");
	if (1 != fwrite(g_hdr_img, g_hdr_img->all_item_count * sizeof(MINI_ITEM_INFO) + sizeof(MINI_HDR), 1, f)) {
		fclose(f);
		MINIDB_DEBUG("[ERR] open db failed.");
		return -1;
	}
	if (1 != fwrite(data_buf, data_len, 1, f)) {
		fclose(f);
		MINIDB_DEBUG("[ERR] write db failed.");
		return -1;
	}
	fclose(f);
	return 0;
}

static int minidb_add_new(int rid, int idx, char* buf, int size) {
	int offset = 0;
	MINI_ITEM_INFO* p = NULL;

	FILE* f = fopen(g_db_dir, "rb+");
	if (!f) {
		MINIDB_DEBUG("[ERR] open db failed.");
		return -1;
	}

	g_hdr_img->used_item_count += 1;

	fseek(f, 0, SEEK_END);
	offset = ftell(f);

	//sync hdr
	p = (MINI_ITEM_INFO*)((char*)g_hdr_img + sizeof(MINI_HDR));
	p[g_hdr_img->used_item_count - 1].rid = rid;
	p[g_hdr_img->used_item_count - 1].index = idx;
	p[g_hdr_img->used_item_count - 1].offset = offset;
	p[g_hdr_img->used_item_count - 1].len = size;

	fseek(f, 0, SEEK_SET);
	if (1 != fwrite(g_hdr_img, sizeof(MINI_HDR) + sizeof(MINI_ITEM_INFO) * g_hdr_img->all_item_count, 1, f)) {
		MINIDB_DEBUG("[ERR] write db failed.");
		fclose(f);
		return -1;
	}

	//sync body
	fseek(f, 0, SEEK_END);
	if (1 != fwrite(buf, size, 1, f)) {
		MINIDB_DEBUG("[ERR] write db failed.");
		fclose(f);
		return -1;
	}

	fclose(f);
	return 0;
}

static int read_item(MINI_ITEM_INFO* it, char** buf, int* readed_size) {
	FILE* f = NULL;
	size_t ret = 0;
	*buf = (char*)malloc(it->len);

	f = fopen(g_db_dir, "rb");
	if (0 != fseek(f, it->offset, SEEK_SET)) {
		MINIDB_DEBUG("[ERR] fseek failed. errno=0x%X", errno);
	}
	ret = fread(*buf, it->len, 1, f);
	if (1 != ret) {
		free(*buf);
		fclose(f);
		MINIDB_DEBUG("[ERR] read db failed. errno=0x%X", errno);
		return 0;
	}
	fclose(f);
	*readed_size = it->len;
	return it->len;
}

//ret: readed char count
int minidb_read(int rid, int idx, char** buf, int* readed_size) {
	MINI_ITEM_INFO* it = 0;
	*buf = 0;
	*readed_size = 0;
	if (!mini_db_already_exist(rid, idx, &it)) {
		MINIDB_DEBUG("[ERR] can not find this item");
		return 0;
	}
	return read_item(it, buf, readed_size);
}

int minidb_write(int rid, int idx, char* buf, int size) {
	MINI_ITEM_INFO* it = 0;
	if (mini_db_already_exist(rid, idx, &it)) {
		return mini_db_update_data(it, buf, size);
	}

	if (minidb_count_reach_max()) {
		minidb_realoc();
	}
	minidb_add_new(rid, idx, buf, size);
	return 0;
}

int mini_init(const char* db_full_path) {
	int l = 0;
	FILE* f = NULL;

	g_db_dir = db_full_path;

	//exist?
	f = fopen(g_db_dir, "rb");
	if (f) {
		MINI_HDR hdr;
		if (1 != fread(&hdr, sizeof(MINI_HDR), 1, f)) {
			MINIDB_DEBUG("[ERR] read db failed.");
			fclose(f);
			return -1;
		}

		l = sizeof(MINI_HDR) + sizeof(MINI_ITEM_INFO) * hdr.all_item_count;
		g_hdr_img = (MINI_HDR*)malloc(l);
		fseek(f, 0, SEEK_SET);
		if (1 != fread(g_hdr_img, l, 1, f)) {
			MINIDB_DEBUG("[ERR] read db failed.");
			fclose(f);
			return -1;
		}
		fclose(f);
		return 0;
	}

	f = fopen(g_db_dir, "wb");
	if (!f) {
		MINIDB_DEBUG("[ERR] can not create db file");
		return -1;
	}
	l = sizeof(MINI_HDR) + ITEM_INIT_COUNT * sizeof(MINI_ITEM_INFO);
	g_hdr_img = (MINI_HDR*)malloc(l);
	memset(g_hdr_img, 0, l);

	g_hdr_img->db_version = DB_VERSION;
	g_hdr_img->all_item_count = ITEM_INIT_COUNT;
	if (1 != fwrite(g_hdr_img, l, 1, f)) {
		MINIDB_DEBUG("[ERR] write db err.");
		fclose(f);
		return -1;
	}
	fclose(f);
	MINIDB_DEBUG("[WARN] %s was create.", g_db_dir);
	return 0;
}

int minidb_ls() {
	int i = 0;

	MINI_ITEM_INFO* p = (MINI_ITEM_INFO*)((char*)g_hdr_img + sizeof(MINI_HDR));
	printf("--------------------LS START---------------------\r\n");
	printf("   rid      idx\r\n");
	for (i = 0; i < g_hdr_img->used_item_count; i++) {
		printf("0x%08x 0x%08x\r\n", p[i].rid, p[i].index);
	}
	printf("--------------------LS END---------------------\r\n");
	printf("total:%d\r\n", g_hdr_img->used_item_count);
	return 0;
}